use std::collections::BTreeMap;
use std::io::{Read, Seek};
use std::convert::TryInto;

use linked_hash_map::LinkedHashMap;
use log::{error, trace};

use crate::{core::{
        FLV_SCRIPT_TYPE_BOOLEAN, FLV_SCRIPT_TYPE_DATE, FLV_SCRIPT_TYPE_ECMA_ARRAY,
        FLV_SCRIPT_TYPE_NULL, FLV_SCRIPT_TYPE_NUMBER, FLV_SCRIPT_TYPE_OBJECT,
        FLV_SCRIPT_TYPE_STRING,
        FLV_SCRIPT_TYPE_SCRIPT_DATA_OBJECT_END,
        FLV_SCRIPT_TYPE_STRICT_ARRAY,
        FLV_SCRIPT_TYPE_UNDEFINED,
        FLV_SCRIPT_TYPE_REFERENCE,
        FLV_SCRIPT_TYPE_LONG_STRING,
        FLV_SCRIPT_TYPE_MOVIE_CLIP
    }, io::{self, DataReader, IOError}};

#[derive(Debug)]
pub enum AMFError {
    KeyError(String),
    TypeError(u8, String),
    IoError(io::IOError),
}

impl std::fmt::Display for AMFError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::KeyError(s) => write!(f, "Key Error: {}", s),
            Self::TypeError(t, s) => write!(f, "Type Error: {}, {}", t, s),
            Self::IoError(e) => write!(f, "I/O Error: {}", e),
        }
    }
}

#[derive(Clone, PartialEq)]
pub enum Value {
    Number(f64),
    Boolean(bool),
    Strings(String),
    Object(LinkedHashMap<String, Value>),
    MovieClip(String),
    Null,
    Undefined,
    Reference,
    EcmaArray(Vec<Value>),
    ObjectEndMarker,
    StrictArray(Vec<Value>),
    Date(f64),
    LongString(String),
    None,
}

impl Value {
    pub fn to_bytes(&self) -> Vec<u8> {
        match self {
            Value::Number(v) => {
                let mut val = vec![FLV_SCRIPT_TYPE_NUMBER];
                val.append(&mut v.to_be_bytes().to_vec());
                val
            },
            Value::Boolean(v) => {
                let mut val = vec![FLV_SCRIPT_TYPE_BOOLEAN];
                val.push(match v {
                    true => 1u8,
                    false => 2u8,
                });
                val
            },
            Value::Strings(v) => {
                let mut val = vec![FLV_SCRIPT_TYPE_STRING];
                val.append(&mut (v.len() as u16).to_be_bytes().to_vec());
                val.append(&mut v.as_bytes().to_vec());
                val
            },
            Value::Object(m) => {
                let mut val = vec![FLV_SCRIPT_TYPE_OBJECT];
                for (k, v) in m.iter() {
                    val.append(&mut (k.len() as u16).to_be_bytes().to_vec());
                    val.append(&mut k.as_bytes().to_vec());
                    val.append(&mut v.to_bytes());
                }
                val.append(&mut vec![0, 0, FLV_SCRIPT_TYPE_SCRIPT_DATA_OBJECT_END]);
                val
            },
            Value::MovieClip(_) => panic!("not support type {}", self),
            Value::Null => vec![FLV_SCRIPT_TYPE_NULL],
            Value::Undefined => vec![FLV_SCRIPT_TYPE_UNDEFINED],
            Value::Reference => vec![FLV_SCRIPT_TYPE_UNDEFINED],
            Value::EcmaArray(arr) => {
                let mut val = vec![FLV_SCRIPT_TYPE_ECMA_ARRAY];
                val.append(&mut arr.len().to_be_bytes().to_vec());
                for v in arr {
                    val.append(&mut v.to_bytes());
                }
                val
            },
            Value::ObjectEndMarker => vec![0, 0, FLV_SCRIPT_TYPE_SCRIPT_DATA_OBJECT_END],
            Value::StrictArray(arr) => {
                let mut val = vec![FLV_SCRIPT_TYPE_STRICT_ARRAY];
                val.append(&mut arr.len().to_be_bytes().to_vec());
                for v in arr {
                    val.append(&mut v.to_bytes());
                }
                val.append(&mut vec![0, 0, FLV_SCRIPT_TYPE_SCRIPT_DATA_OBJECT_END]);
                val
            },
            Value::Date(v) => {
                let mut val = vec![FLV_SCRIPT_TYPE_DATE];
                val.append(&mut v.to_be_bytes().to_vec());
                val
            },
            Value::LongString(v) => {
                let mut val = vec![FLV_SCRIPT_TYPE_LONG_STRING];
                val.append(&mut (v.len() as u32).to_be_bytes().to_vec());
                val.append(&mut v.as_bytes().to_vec());
                val
            },
            Value::None => Vec::new(),
        }
    }
}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Number(v) => write!(f, "Number({})", v),
            Self::Boolean(v) => write!(f, "Boolean({})", v),
            Self::Strings(v) => write!(f, "Strings(\"{}\")", v),
            Self::Object(v) => write!(f, "Object({:?})", v),
            Self::MovieClip(v) => write!(f, "MovieClip(\"{}\")", v),
            Self::Null => write!(f, "Null"),
            Self::Undefined => write!(f, "Undefined"),
            Self::Reference => write!(f, "Reference"),
            Self::EcmaArray(v) => write!(f, "EcmaArray({}#{:?})", v.len(), v),
            Self::ObjectEndMarker => write!(f, "ObjectEndMarker"),
            Self::StrictArray(v) => write!(f, "StrictArray({}#{:?})", v.len(), v),
            Self::Date(v) => write!(f, "Date({})", v),
            Self::LongString(v) => write!(f, "LongString(\"{}\")", v),
            Self::None => write!(f, "None"),
        }
    }
}

impl std::fmt::Debug for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Number(v) => write!(f, "Number({})", v),
            Self::Boolean(v) => write!(f, "Boolean({})", v),
            Self::Strings(v) => write!(f, "Strings(\"{}\")", v),
            Self::Object(v) => write!(f, "Object({:?})", v),
            Self::MovieClip(v) => write!(f, "MovieClip(\"{}\")", v),
            Self::Null => write!(f, "Null"),
            Self::Undefined => write!(f, "Undefined"),
            Self::Reference => write!(f, "Reference"),
            Self::EcmaArray(v) => write!(f, "EcmaArray({}#{:?})", v.len(), v),
            Self::ObjectEndMarker => write!(f, "ObjectEndMarker"),
            Self::StrictArray(v) => write!(f, "StrictArray({}#{:?})", v.len(), v),
            Self::Date(v) => write!(f, "Date({})", v),
            Self::LongString(v) => write!(f, "LongString(\"{}\")", v),
            Self::None => write!(f, "None"),
        }
    }
}

impl From<f64> for Value {
    fn from(value: f64) -> Self {
        Value::Number(value)
    }
}

impl TryInto<f64> for Value {
    type Error = AMFError;

    fn try_into(self) -> Result<f64, Self::Error> {
        match self {
            Value::Number(v) | Value::Date(v) => Ok(v),
            _ => Err(AMFError::TypeError(0, format!("type error {:?}", self))),
        }
    }
}

impl From<bool> for Value {
    fn from(value: bool) -> Self {
        Value::Boolean(value)
    }
}

impl TryInto<bool> for Value {
    type Error = AMFError;

    fn try_into(self) -> Result<bool, Self::Error> {
        match self {
            Value::Boolean(v) => Ok(v),
            _ => Err(AMFError::TypeError(0, format!("type error {}", self))),
        }
    }
}

impl From<String> for Value {
    fn from(value: String) -> Self {
        Value::Strings(value)
    }
}

impl TryInto<String> for Value {
    type Error = AMFError;

    fn try_into(self) -> Result<String, Self::Error> {
        match self {
            Value::LongString(v) | Value::Strings(v) => Ok(v),
            _ => Err(AMFError::TypeError(0, format!("type error {:?}", self))),
        }
    }
}

impl From<Vec<Value>> for Value {
    fn from(value: Vec<Value>) -> Self {
        Value::StrictArray(value)
    }
}

impl Into<Vec<Value>> for Value {
    fn into(self) -> Vec<Value> {
        match self {
            Value::StrictArray(v) | Value::EcmaArray(v) => v,
            _ => Vec::new(),
        }
    }
}

impl TryInto<Vec<Value>> for &Value {
    type Error = AMFError;

    fn try_into(self) -> Result<Vec<Value>, Self::Error> {
        match self {
            Value::StrictArray(v) | Value::EcmaArray(v) => Ok(v.to_vec()),
            _ => Err(AMFError::TypeError(0, format!("type error {:?}", self))),
        }
    }
}

impl TryInto<Vec<f64>> for &Value {
    type Error = AMFError;

    fn try_into(self) -> Result<Vec<f64>, Self::Error> {
        match self {
            Value::StrictArray(vals) | Value::EcmaArray(vals) => {
                let mut new_vals: Vec<f64> = Vec::new();
                for v in vals {
                    match v {
                        Value::Number(v) => new_vals.push(*v),
                        _ => return Err(AMFError::TypeError(0, format!("type error {:?}", self))),
                    }
                }
                Ok(new_vals)
            }
            _ => Err(AMFError::TypeError(0, format!("type error {:?}", self))),
        }
    }
}

impl From<LinkedHashMap<String, Value>> for Value {
    fn from(value: LinkedHashMap<String, Value>) -> Self {
        Value::Object(value)
    }
}

impl TryInto<LinkedHashMap<String, Value>> for Value {
    type Error = AMFError;

    fn try_into(self) -> Result<LinkedHashMap<String, Value>, Self::Error> {
        match self {
            Value::Object(v) => Ok(v),
            _ => Err(AMFError::TypeError(0, format!("type error {:?}", self))),
        }
    }
}

pub struct AMF;

impl AMF {
    pub fn parse_variable<R>(di: &mut DataReader<R>) -> Result<(String, Value), AMFError>
    where R: Read + Seek
    {
        let key_len = di.read_u16();
        let key = match key_len {
            Ok(v) if v > 0 => di.read_string(key_len.unwrap() as u64),
            Ok(_) => Ok(String::new()),
            Err(e) => Err(e),
        };
        // if key_len.is_err() {
        //     let e = AMFError::KeyError(format!("read key length error: {}", key_len.unwrap_err()));
        //     error!("{:?}", e);
        //     return Err(e);
        // }
        // let key = di.read_string(key_len.unwrap() as u64);
        if key.is_err() {
            let e = AMFError::KeyError(format!("read key error: {}", key.unwrap_err()));
            error!("{:?}", e);
            return Err(e);
        }
        let val = AMF::parse_value(di);
        trace!("amf parse variable: {:?}, {:?}", key, val);
        match val {
            Ok(v) => Ok((key.unwrap(), v)),
            Err(e) => Err(e),
        }
    }
    pub fn  parse_value<R>(di: &mut DataReader<R>) -> Result<Value, AMFError>
    where R: Read + Seek
    {
        let val_type = di.read_u8();
        let val: Result<Value, AMFError> = match val_type {
            Ok(FLV_SCRIPT_TYPE_NUMBER) => match di.read_f64() {
                Ok(v) => Ok(Value::Number(v)),
                Err(e) => Err(AMFError::IoError(e)),
            },
            Ok(FLV_SCRIPT_TYPE_BOOLEAN) => match di.read_u8() {
                Ok(v) => Ok(Value::Boolean(v == 1)),
                Err(e) => Err(AMFError::IoError(e)),
            },
            Ok(FLV_SCRIPT_TYPE_STRING) => {
                let len = di.read_u16().unwrap();
                match di.read_string(len as u64) {
                    Ok(v) => Ok(Value::Strings(v)),
                    Err(e) => Err(AMFError::IoError(e)),
                }
            }
            Ok(FLV_SCRIPT_TYPE_OBJECT) => {
                let mut m: LinkedHashMap<String, Value> = LinkedHashMap::new();
                loop {
                    let di_ = &mut *di;
                    match AMF::parse_variable(di_) {
                        Ok((key, val)) => {
                            match val {
                                Value::None
                                | Value::Null
                                | Value::Undefined
                                | Value::MovieClip(_)
                                | Value::Reference => continue,
                                Value::ObjectEndMarker => break,
                                _ => m.insert(key, val),
                            };
                        }
                        Err(e) => {
                            error!("read object error: {}", e);
                            break;    
                        }
                    }
                }
                trace!("{:?}", di);
                Ok(Value::Object(m))
            },
            Ok(FLV_SCRIPT_TYPE_MOVIE_CLIP) => Ok(Value::MovieClip("Not Support".to_string())),
            Ok(FLV_SCRIPT_TYPE_NULL) => Ok(Value::Null),
            Ok(FLV_SCRIPT_TYPE_UNDEFINED) => Ok(Value::Undefined),
            Ok(FLV_SCRIPT_TYPE_REFERENCE) => Ok(Value::Reference),
            Ok(FLV_SCRIPT_TYPE_ECMA_ARRAY) => {
                let mut arr: Vec<Value> = Vec::new();
                let arr_len = di.read_u32().unwrap();
                for _ in 0..arr_len {
                    match AMF::parse_value(di) {
                        Ok(v) => match v {
                            _ => arr.push(v),
                        },
                        Err(_) => continue,
                    };
                }
                Ok(Value::EcmaArray(arr))
            }
            Ok(FLV_SCRIPT_TYPE_SCRIPT_DATA_OBJECT_END) => Ok(Value::ObjectEndMarker),
            Ok(FLV_SCRIPT_TYPE_STRICT_ARRAY) => {
                let mut arr: Vec<Value> = Vec::new();
                let arr_len = di.read_u32().unwrap();
                trace!("strict array length: {}", arr_len);
                for _ in 0..arr_len {
                    match AMF::parse_value(di) {
                        Ok(v) => match v {
                            _ => arr.push(v),
                        },
                        Err(_) => continue,
                    };
                }
                match AMF::next_is_end(di) {
                    Ok(_) => Ok(Value::StrictArray(arr)),
                    Err(AMFError::IoError(IOError::ReadCompleted(_, _))) => Ok(Value::StrictArray(arr)),
                    Err(e) => Err(e),
                }
                
            },
            Ok(FLV_SCRIPT_TYPE_DATE) => {
                let timestamp = di.read_f64().unwrap();
                let local_time_offset = di.read_u16().unwrap();
                Ok(Value::Date(
                    timestamp + (local_time_offset as f64) * 60f64 * 1000f64,
                ))
            },
            Ok(FLV_SCRIPT_TYPE_LONG_STRING) => {
                let len = di.read_u32().unwrap();
                match di.read_string(len as u64) {
                    Ok(v) => Ok(Value::Strings(v)),
                    Err(e) => Err(AMFError::IoError(e)),
                }
            },
            Ok(v) => Err(AMFError::TypeError(v, format!("type {} error", v))),
            Err(e) => Err(AMFError::IoError(e)),
        };

        match val {
            Ok(v) => Ok(v),
            Err(e) => Err(e),
        }
    }

    pub fn next_is_end<R>(di: &mut DataReader<R>) -> Result<bool, AMFError>
    where R: Read + Seek
    {
        match di.read_to_u64_not_skip(3) {
            Ok(v) => Ok(v == (FLV_SCRIPT_TYPE_SCRIPT_DATA_OBJECT_END as u64)),
            Err(e) => Err(AMFError::IoError(e))
        }
    }
}
